package springboard.auth.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import common.mybatis.Queries;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import springboard.auth.bean.Account;
import springboard.auth.bean.Permission;
import springboard.auth.bean.Role;
import springboard.auth.bean.User;
import springboard.auth.dao.AccountMapper;
import springboard.auth.dao.RoleMapper;
import springboard.auth.dao.UserMapper;

import javax.annotation.Nullable;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class AuthServiceImplImpl implements AuthService {

    @Autowired
    AccountMapper accountMapper;

    @Autowired
    RoleMapper roleMapper;

    @Autowired
    UserMapper userMapper;

    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

    AuthPredefinitions authPredefinitions = AuthPredefinitions.load();

    @Override
    public User getUser(long id) {
        return userMapper.selectById(id);
    }

    @Override
    public Page<User> listUsers(@Nullable String keyword, @Nullable Date createdTime0, @Nullable Date createdTime1, int... pagination) {
        LambdaQueryWrapper<User> criteria = new QueryWrapper<User>().lambda();
        if (keyword != null)
            criteria.eq(User::getId, keyword).or().like(User::getNickname, keyword).or().like(User::getPhone, keyword).or().like(User::getMail, keyword);
        if (createdTime0 != null) criteria.ge(User::getCreatedTime, createdTime0);
        if (createdTime1 != null) criteria.lt(User::getCreatedTime, createdTime1);
        return Queries.paginate(() -> userMapper.selectList(criteria), pagination);
    }

    @Override
    public List<User> findUserByPhone(String phone, Short phoneStatus) {
        LambdaQueryWrapper<User> criteria = new QueryWrapper<User>().lambda();
        criteria.eq(User::getPhone, phone);
        if(phoneStatus != null) criteria.eq(User::getPhoneStatus, phoneStatus);
        criteria.orderByDesc(User::getUpdatedTime);
        return userMapper.selectList(criteria);
    }

    @Override
    public User createUser(User user) {
        if (user.getStatus() == null) user.setStatus((short) 0);
        user.setCreatedTime(new Date());
        int r = userMapper.insert(user);
        return r > 0 ? user : null;
    }

    @Override
    public User updateUser(User user) {
        User oldUser = getUser(user.getId());
        if (StringUtils.hasText(user.getPhone()) && !user.getPhone().equals(oldUser.getPhone()))
            user.setPhoneStatus((short) 0);
        if (StringUtils.hasText(user.getMail()) && !user.getMail().equals(oldUser.getMail()))
            user.setMailStatus((short) 0);
        user.setUpdatedTime(new Date());
        int r = userMapper.updateById(user);
        return r > 0 ? user : null;
    }

    @Override
    public Permission getPermission(String key) {
        return authPredefinitions.findPermission(key);
    }

    @Override
    public List<Permission> listPermissions() {
        return authPredefinitions.findPermissions();
    }

    @Override
    public Role getRole(String key) {
        Role role = authPredefinitions.findRole(key);
        if (role == null) role = roleMapper.selectById(key);
        return role;
    }

    @Override
    public List<Role> listRoles() {
        List<Role> roles = authPredefinitions.findRoles();
        roles.addAll(roleMapper.selectAll());
        return roles;
    }

    @Override
    public Role createRole(Role role) {
        int r = roleMapper.insert(role);
        return r > 0 ? role : null;
    }

    @Override
    @Transactional
    public Role updateRole(Role role, String oldKey) {
        int r = roleMapper.update(role, oldKey);
        if (!role.getKey().equals(oldKey)) {
            r += roleMapper.updateAllUserRole(role.getKey(), oldKey);
            r += roleMapper.updateAllRolePermission(role.getKey(), oldKey);
        }
        return r > 0 ? role : null;
    }

    @Override
    @Transactional
    public int deleteRole(String key) {
        int r = roleMapper.delete(key);
        r += roleMapper.deleteAllUserRole(key);
        r += roleMapper.deleteAllRolePermission(key);
        return r;
    }

    @Override
    public Account getAccount(String username) {
        return accountMapper.selectById(username);
    }

    @Override
    public List<Account> findUserAccounts(long userId) {
        return accountMapper.selectByUserId(userId);
    }

    @Override
    public User findUserByUsername(String username) {
        return accountMapper.selectUserByUsername(username);
    }

    @Override
    public User authc(String username, String password) {
        Account account = accountMapper.selectById(username);
        if (account == null) return null;
        return authc(account, password);
    }

    @Override
    public User authc(Account account, String password) {
        if (!passwordEncoder.matches(password, account.getPassword()))
            throw new BadCredentialsException(password);
        return userMapper.selectById(account.getUserId());
    }

    @Override
    public Account createAccount(Account account) {
        if (StringUtils.hasText(account.getPlainPassword()))
            account.setPassword(passwordEncoder.encode(account.getPlainPassword()));
        account.setCreatedTime(new Date());
        int r = accountMapper.insert(account);
        return r > 0 ? account : null;
    }

    @Override
    public Account updateAccount(Account account) {
        if (StringUtils.hasText(account.getPlainPassword()))
            account.setPassword(passwordEncoder.encode(account.getPlainPassword()));
        int r = accountMapper.updateById(account);
        return r > 0 ? account : null;
    }

    @Override
    public String updateUserPassword(long userId, String password) {
        String encodedPassword = passwordEncoder.encode(password);
        accountMapper.updatePasswordByUserId(userId, encodedPassword);
        return encodedPassword;
    }

    @Override
    public List<Permission> findUserPermissions(long userId) {
        return userMapper.selectUserPermissionKeys(userId).stream().map(key -> authPredefinitions.findPermission(key)).collect(Collectors.toList());
    }

    @Override
    public Set<String> findUserPermissionKeys(long userId) {
        return userMapper.selectUserPermissionKeys(userId).stream().collect(Collectors.toSet());
    }

    @Override
    public Set<String> findAllUserPermissionKeys(long userId) {
        Set<String> result = findUserPermissionKeys(userId);
        Set<String> userRoleKeys = findUserRoleKeys(userId);
        for (String userRoleKey : userRoleKeys) {
            result.addAll(findRolePermissionKeys(userRoleKey));
        }
        return result;
    }

    @Override
    @Transactional
    public int giveUserPermissions(long userId, String... permissionKeys) {
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += userMapper.insertUserPermission(userId, permissionKey);
        }
        return result;
    }

    @Override
    @Transactional
    public int ungiveUserPermissions(long userId, String... permissionKeys) {
        if (permissionKeys.length == 0) return userMapper.deleteAllUserPermission(userId);
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += userMapper.deleteUserPermission(userId, permissionKey);
        }
        return result;
    }

    @Override
    public List<Role> findUserRoles(long userId) {
        return userMapper.selectUserRoleKeys(userId).stream().map(key -> getRole(key)).collect(Collectors.toList());
    }

    @Override
    public Set<String> findUserRoleKeys(long userId) {
        return userMapper.selectUserRoleKeys(userId).stream().collect(Collectors.toSet());
    }

    @Override
    @Transactional
    public int giveUserRoles(long userId, String... roleKeys) {
        int result = 0;
        for (String roleKey : roleKeys) {
            result += userMapper.insertUserRole(userId, roleKey);
        }
        return result;
    }

    @Override
    @Transactional
    public int ungiveUserRoles(long userId, String... roleKeys) {
        if (roleKeys.length == 0) return userMapper.deleteAllUserPermission(userId);
        int result = 0;
        for (String roleKey : roleKeys) {
            result += userMapper.deleteUserRole(userId, roleKey);
        }
        return result;
    }

    @Override
    public List<Permission> findRolePermission(String key) {
        Role role = getRole(key);
        if (role.isPredefined())
            return ((AuthPredefinitions.RoleInfo) role).getPermissions().stream().map(s -> getPermission(s)).collect(Collectors.toList());
        else
            return roleMapper.selectRolePermissionKeys(key).stream().map(s -> getPermission(s)).collect(Collectors.toList());
    }

    @Override
    public Set<String> findRolePermissionKeys(String key) {
        Role role = getRole(key);
        if (role.isPredefined())
            return ((AuthPredefinitions.RoleInfo) role).getPermissions().stream().collect(Collectors.toSet());
        else
            return roleMapper.selectRolePermissionKeys(key).stream().collect(Collectors.toSet());
    }

    @Override
    @Transactional
    public int giveRolePermissions(String key, String... permissionKeys) {
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += roleMapper.insertRolePermission(key, permissionKey);
        }
        return result;
    }

    @Override
    @Transactional
    public int ungiveRolePermissions(String key, String... permissionKeys) {
        if (permissionKeys.length == 0) return roleMapper.deleteAllRolePermission(key);
        int result = 0;
        for (String permissionKey : permissionKeys) {
            result += roleMapper.deleteRolePermission(key, permissionKey);
        }
        return result;
    }

    @Override
    public Page<Account> listUserAccounts(String keyword, int[] pagination) {
        LambdaQueryWrapper<Account> criteria = new QueryWrapper<Account>().lambda();
        if (keyword != null) criteria.like(Account::getUsername, keyword);
        Page<Account> paginate = Queries.paginate(() -> accountMapper.selectList(criteria), pagination);
        List<Account> accountList = paginate.getContent();
        if (accountList.size() == 0) return paginate;
        Set<Long> userIds = accountList.stream().map(Account::getUserId).collect(Collectors.toSet());
        List<User> users = userMapper.selectBatchIds(userIds);
        for (Account account : accountList) {
            List<User> userList = users.stream().filter(u -> Objects.equals(u.getId(), account.getUserId())).collect(Collectors.toList());
            if (userList.size() == 1) {
                account.setUser(userList.get(0));
            }
        }
        return paginate;
    }
}
